/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Xceed.Silverlight.Data
{
  internal class DataPath : IEnumerable<DataItem>
  {
    public static readonly DataPath Empty = new DataPath( new DataItem[ 0 ] );

    // Create an instance with a list of 1 item.
    public DataPath( DataItem obj )
    {
      if( obj == null )
        throw new ArgumentNullException( "obj" );

      m_path = new DataItem[] { obj };
    }

    public DataPath( DataItem[] objectPath )
      : this( objectPath, objectPath.Length, false )
    {
    }

    public DataPath( DataItem[] objectPath, int length )
      : this( objectPath, length, false )
    {
    }

    internal DataPath( DataItem[] objectPath, bool useClientArray )
      : this( objectPath, objectPath.Length, useClientArray )
    {
    }

    internal DataPath( DataItem[] objectPath, int length, bool useClientArray )
    {
      if( objectPath == null )
        throw new ArgumentNullException( "objectPath" );

      if( length < 0 )
        throw new ArgumentOutOfRangeException( "length", "length must be greater than or equal to zero." );

      if( objectPath.Length < length )
        throw new ArgumentException( "The length of the specified DataItem array must be greater than or equal to the specified length.", "objectPath" );

      for( int i = 0; i < length; i++ )
      {
        if( objectPath[ i ] == null )
          throw new ArgumentException( "The specified DataItem array cannot contain null items.", "objectPath" );
      }

      if( !useClientArray )
      {
        DataItem[] newPath = new DataItem[ length ];
        Array.Copy( objectPath, newPath, length );
        m_path = newPath;
      }
      else
      {
        if(length != objectPath.Length)
          throw new InvalidOperationException( "The objectPath length is inconsistent with the length parameter." );

        m_path = objectPath;
      }
    }

    public DataPath( DataItem[] parentPath, DataItem child )
    {
      if( parentPath == null )
        throw new ArgumentNullException( "parentPath" );

      if( child == null )
        throw new ArgumentNullException( "child" );

      m_path = new DataItem[ parentPath.Length + 1 ];
      Array.Copy( parentPath, m_path, parentPath.Length );
      m_path[ m_path.Length - 1 ] = child;
    }


    public DataPath( DataItem rootItem, DataItem[] children )
    {
      if( rootItem == null )
        throw new ArgumentNullException( "rootItem" );

      if( children == null )
        throw new ArgumentNullException( "children" );

      m_path = new DataItem[ children.Length + 1 ];
      m_path[ 0 ] = rootItem;
      Array.Copy( children, 0, m_path, 1, children.Length );
    }


    public DataPath ParentPath
    {
      get
      {
        if( m_path.Length == 1 )
          return null;
        DataItem[] ret = new DataItem[ m_path.Length - 1 ];
        Array.Copy( m_path, ret, ret.Length );
        return new DataPath( ret, true );
      }
    }

    public DataItem LastChild
    {
      get
      {
        return m_path[ m_path.Length - 1 ];
      }
    }

    public int Depth
    {
      get
      {
        return m_path.Length;
      }
    }

    internal DataItem[] Path
    {
      get
      {
        return m_path;
      }
    }

    public DataItem this[ int index ]
    {
      get
      {
        if( index >= this.Depth )
          throw new ArgumentOutOfRangeException( "index", "index must be less than the path depth.");

        return m_path[ index ];
      }
      internal set
      {
        if( index >= this.Depth )
          throw new ArgumentOutOfRangeException( "index", "index must be less than the path depth." );

        if( value == null )
          throw new ArgumentNullException( "Index" );

        m_path[ index ] = value;

      }
    }

    public DataPath CreateChildPath( DataItem child )
    {
      return new DataPath( this.m_path, child );
    }

    public DataPath CreateChildPath( DataPath childPath, int startIndex, int length )
    {
      int childDepth = childPath.Depth;

      if( startIndex >= childDepth )
        throw new ArgumentOutOfRangeException( "startIndex", "startIndex must be less than the childPath's depth." );

      if( startIndex + length > childDepth )
        throw new ArgumentOutOfRangeException( "length", "length must be less than or equal to the childPath's scope." );

      DataItem[] newArray = new DataItem[ this.Depth + length ];

      Array.Copy( m_path, 0, newArray, 0, m_path.Length );
      Array.Copy( childPath.m_path, startIndex, newArray, m_path.Length, length );

      return new DataPath( newArray, true );
    }

    public DataPath CreateAncestorPath( int depth )
    {
      if( depth > this.Depth )
        throw new ArgumentOutOfRangeException( "depth", "depth must be less than or equal to the actual path depth." );

      return this.CreateAncestorOrSamePath( depth );
    }

    public DataPath CreateAncestorOrSamePath( int depth )
    {

      DataItem[] newArray = new DataItem[ depth ];

      Array.Copy( this.m_path, 0, newArray, 0, depth );

      DataPath newPath = new DataPath( newArray, true );

      return newPath;
    }

    // of the current path.
    public bool IsAncestorOf( DataPath childPath )
    {
      if( this.m_path.Length < childPath.m_path.Length )
      {
        return DataPath.AreSameItems( m_path, childPath.m_path, m_path.Length );
      }
      return false;
    }

    internal bool IsAncestorOf( DataItem[] arrayPath, int validDepth )
    {
      if( this.m_path.Length < validDepth ) // childPath.m_path.Length )
      {
        return DataPath.AreSameItems( m_path, arrayPath, m_path.Length );
      }
      return false;
    }

    public DataItem[] ToArray()
    {
      DataItem[] newArray = new DataItem[ this.Depth ];
      m_path.CopyTo( newArray, 0 );
      return newArray;
    }

    internal void InsertItems( DataItem[] dataItems, int index )
    {
      if( dataItems == null )
        throw new ArgumentNullException( "dataItems" );

      if( index < 0 || index > m_path.Length )
        throw new ArgumentOutOfRangeException( "index", "index must be greater than or equal to zero, and less than or equal to the length of the data path." );

      if( dataItems.Any( ( item ) => item == null ) )
        throw new ArgumentException( "The specified DataItem array cannot contain null items.", "dataItems" );

      DataItem[] newPath = new DataItem[ m_path.Length + dataItems.Length ];
      Array.Copy( m_path, 0, newPath, 0, index );
      Array.Copy( dataItems, 0, newPath, index, dataItems.Length );
      Array.Copy( m_path, index, newPath, index + dataItems.Length, m_path.Length - index );
      m_path = newPath;
    }   

    public bool IsDescendantOf( DataPath parentPath )
    {
      if( parentPath == null )
        throw new ArgumentNullException( "parentPath" );

      return parentPath.IsAncestorOf( this );
    }

    public override int GetHashCode()
    {
      int hashcode = 0;

      int length = m_path.Length;
      for( int i = 0; i < length; i++ )
      {
        hashcode = hashcode ^ m_path[ i ].GetHashCode();
      }

      return hashcode;
    }

    public override bool Equals( object obj )
    {
      DataPath nodePath = obj as DataPath;
      if( ( nodePath == null ) || this.m_path.Length != nodePath.m_path.Length )
        return false;

      return DataPath.AreSameItems( m_path, nodePath.m_path, m_path.Length );
    }

    static internal bool AreSameItems( DataItem[] array1, DataItem[] array2, int len )
    {
      for( int i = 0; i < len; i++ )
      {
        if( !object.Equals( array1[ i ], array2[ i ] ) )
        {
          return false;
        }
      }
      return true;
    }

#if DEBUG
    public override string ToString()
    {
      if( m_path.Length == 0 )
        return "(Empty Path)";

      StringBuilder sb = new StringBuilder();
      bool first = true;
      foreach( DataItem dgi in m_path )
      {
        if( !first )
        {
          sb.Append( @"\" );
        }
        first = false;
        sb.Append( dgi.ToString() );
      }
      return sb.ToString();
    }
#endif


    #region IEnumerable<DataItem> Members

    // with the topmost parent.
    public IEnumerator<DataItem> GetEnumerator()
    {
      foreach( DataItem dgi in m_path )
      {
        yield return dgi;
      }
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }

    #endregion

    private DataItem[] m_path;
  }
}
